package com.naico.util;

import org.apache.commons.lang.StringUtils;

import java.text.Format;
import java.util.ArrayList;
import java.util.List;

public class TableFormatter {
    private static final String newline = System.getProperty("line.separator");
    private int numRows = 0;
    private final List<Column> columns = new ArrayList<Column>();

    public void addColumn(String name, int width, Format format,
                          boolean isLeftJustified) {
        if (width <= 0) {
            throw new IllegalArgumentException(
                    "Invalid column width: " + width);
        }

        columns.add(new Column(name, width, format, isLeftJustified));
    }

    public void addColumn(String name, int width, boolean isLeftJustified) {
        addColumn(name, width, null, isLeftJustified);
    }

    public void addColumn(String name, int width) {
        addColumn(name, width, null, true);
    }

    public void setData(int row, int col, Object data) {
        if (row < 0) {
            throw new IndexOutOfBoundsException("Invalid row: " + row);
        }

        // Get the column that we want to add to
        Column column = columns.get(col);

        // Expand column if necessary
        numRows = Math.max(numRows, row + 1);
        column.data.ensureCapacity(numRows);

        for (int i = column.data.size(); i <= row; i++) {
            column.data.add(null);
        }

        // Now put in the data
        column.data.set(row, data);
    }

    public String toString() {
        if (columns.isEmpty()) {
            return StringUtils.EMPTY;
        }

        // First figure out the approximate size in chars of a row
        int rowSize = 0;

        for (Column column : columns) {
            rowSize += column.width;
        }

        rowSize += (columns.size() - 1); // add room for column separators

        // Create the buffer
        StringBuffer buf = new StringBuffer(numRows * rowSize);

        // Print the header
        for (int i = 0; i < columns.size(); i++) {
            if (i > 0) {
                buf.append(' ');
            }

            Column column = columns.get(i);
            append(buf, column.name, column.width, null, true);
        }

        buf.append(newline);

        // Print a separator line
        buf.append(StringUtils.leftPad(StringUtils.EMPTY, rowSize, '-'));
        buf.append(newline);

        // Now print data
        for (int i = 0; i < numRows; i++) {
            for (int j = 0; j < columns.size(); j++) {
                if (j > 0) {
                    buf.append(' ');
                }

                Column column = columns.get(j);
                Object data =
                        (i < column.data.size()) ? column.data.get(i) : null;
                append(
                        buf,
                        data,
                        column.width,
                        column.format,
                        column.isLeftJustified);
            }

            buf.append(newline);
        }

        return buf.toString();
    }

    private void append(StringBuffer buf, Object data, int width,
                        Format format, boolean isLeftJustified) {
        if (data == null) {
            append(buf, "null", width, null, true);
        } else {
            String value =
                    (format != null) ? format.format(data) : data.toString();
            int size = value.length();

            // Pad or truncate as necessary
            if (size < width) {
                if (isLeftJustified) {
                    value = StringUtils.rightPad(value, width);
                } else {
                    value = StringUtils.leftPad(value, width);
                }
            } else if (size > width) {
                value = value.substring(0, width);
            }

            // Now append
            buf.append(value);
        }
    }

    private static class Column {
        public final String name;
        public final int width;
        public final Format format;
        public final boolean isLeftJustified;
        public final ArrayList data;

        public Column(String name, int width, Format format,
                      boolean isLeftJustified) {
            this.name = name;
            this.width = width;
            this.format = format;
            this.isLeftJustified = isLeftJustified;
            this.data = new ArrayList();
        }
    }
}
